package org.giorgiocalderolla.showrssx;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Observable;
import java.util.prefs.Preferences;

/**
 * @author Giorgio Calderolla
 * 
 *         This class manages the user configuration.
 */
public class Configuration extends Observable {

	// objects and keys for the java Preferences API
	private Preferences preferences = null;
	public static final String PREFERENCE_CONFIGURED = "configured";
	public static final String PREFERENCE_FEED_URL = "feedUrl";
	public static final String PREFERENCE_TORRENT_FOLDER = "torrentFolder";
	public static final String PREFERENCE_UPDATE_INTERVAL = "updateInterval";
	public static final String PREFERENCE_LAST_UPDATE_TIME = "lastUpdateTime";
	public static final String PREFERENCE_LAST_UPDATE_FAILED = "lastUpdateFailed";
	public static final String PREFERENCE_DOWNLOADED_TORRENTS = "downloadedTorrents";

	// keys for non-persistent status information
	public static final String PREFERENCE_STATUS = "currentStatus";
	public static final String PREFERENCE_PROBLEMS = "currentProblems";

	// defaults
	public static final int DEFAULT_UPDATE_INTERVAL = 10;
	public static final String DEFAULT_LAST_UPDATE_TIME = "Never";
	public static final boolean DEFAULT_LAST_UPDATE_FAILED = false;
	public static final ArrayList<String> DEFAULT_DOWNLOADED_TORRENTS = new ArrayList<String>();
	public static final String DEFAULT_TORRENT_FOLDER = "/Users/"
			+ System.getProperty("user.name") + "/Downloads";

	// allowed values
	public static final int ALLOWED_UPDATE_INTERVALS[] = { 10, 15, 20, 30, 60 };

	public enum Status {
		DISABLED, ACTIVE_IDLE, ACTIVE_WORKING
	};

	// instance variables, for non-persistent status information
	private Status status = Status.ACTIVE_IDLE;
	private ArrayList<Problem> problems = new ArrayList<Problem>();

	/**
	 * Instantiates the configuration manager
	 */
	public Configuration() {

		preferences = Preferences.systemNodeForPackage(this.getClass());
	}

	/**
	 * This is called whenever the configuration changes, and notifies whoever
	 * is observing it
	 */
	private void configurationChanged(Object changedPreference) {

		setChanged();
		notifyObservers(changedPreference);
	}

	/**
	 * @param updateInterval
	 *            the interval (in minutes) between feed downloads
	 */
	public void setUpdateInterval(int updateInterval) {

		if (checkUpdateInterval(updateInterval)) {
			preferences.putInt(PREFERENCE_UPDATE_INTERVAL, updateInterval);
			configurationChanged(PREFERENCE_UPDATE_INTERVAL);
		}
	}

	/**
	 * @return the interval (in minutes) between feed downloads
	 */
	public int getUpdateInterval() {

		return preferences.getInt(PREFERENCE_UPDATE_INTERVAL,
				DEFAULT_UPDATE_INTERVAL);
	}

	/**
	 * Validates the update interval: is it in the allowed range?
	 * 
	 * @param updateInterval
	 *            the interval (in minutes) to validate
	 * @return true, always
	 * @throws ConfigurationException
	 *             if the update interval isn't valid
	 */
	private boolean checkUpdateInterval(int updateInterval)
			throws ConfigurationException {

		boolean validInterval = false;

		for (int interval : ALLOWED_UPDATE_INTERVALS) {
			if (interval == updateInterval) {
				validInterval = true;
			}
		}

		if (!validInterval) {
			ConfigurationException ce = new ConfigurationException();
			ce.initCause(new IllegalArgumentException(
					"Update interval not allowed"));
			throw ce;
		}

		return true;
	}

	/**
	 * @param torrentFolder
	 *            the folder where to save downloaded .torrent files
	 * 
	 * @throws ConfigurationException
	 *             if the folder doesn't exist or is not writable
	 */
	public void setTorrentFolder(String torrentFolder)
			throws ConfigurationException {

		if (checkTorrentFolder(torrentFolder)) {
			// ok, we can use this folder
			preferences.put(PREFERENCE_TORRENT_FOLDER, torrentFolder);
			configurationChanged(PREFERENCE_TORRENT_FOLDER);
		}
	}

	/**
	 * @return the folder where to save downloaded .torrent files
	 */
	public String getTorrentFolder() {

		return preferences.get(PREFERENCE_TORRENT_FOLDER,
				DEFAULT_TORRENT_FOLDER);
	}

	/**
	 * Validates the folder: does it exist?
	 * 
	 * @param torrentFolder
	 *            the folder to check
	 * @return true, always
	 * @throws ConfigurationException
	 *             if the folder isn't valid
	 */
	private boolean checkTorrentFolder(String torrentFolder)
			throws ConfigurationException {

		torrentFolder = torrentFolder.trim();

		try {
			if (!new File(torrentFolder).exists())
				throw new IllegalArgumentException("Directory doesn't exist");

		} catch (Exception e) {
			// whoops, not a good folder. we won't allow it in the configuration
			ConfigurationException ce = new ConfigurationException();
			ce.initCause(e);
			throw ce;
		}

		return true;
	}

	/**
	 * @param feedUrl
	 *            url of the feed to check for torrents
	 */
	public void setFeedUrl(String feedUrl) {

		if (checkFeedUrl(feedUrl)) {
			preferences.put(PREFERENCE_FEED_URL, feedUrl);
			configurationChanged(PREFERENCE_FEED_URL);
		}
	}

	/**
	 * @return url of the feed to check for torrents
	 */
	public String getFeedUrl() {

		return preferences.get(PREFERENCE_FEED_URL, "");
	}

	/**
	 * Validates the Feed URL
	 * 
	 * @param feedUrl
	 *            the url to check
	 * @return true, always
	 * @throws ConfigurationException
	 *             if the URL isn't valid
	 */
	private boolean checkFeedUrl(String feedUrl) throws ConfigurationException {

		feedUrl = feedUrl.trim();

		try {
			new URL(feedUrl);
		} catch (MalformedURLException mue) {
			ConfigurationException ce = new ConfigurationException();
			ce.initCause(mue);
			throw ce;
		}

		// we only check to see if the url is from ShowRSS and if it has the
		// user_id parameter
		if (!feedUrl.startsWith("http://showrss.karmorra.info/rss.php?")
				|| !feedUrl.contains("user_id=")) {
			ConfigurationException ce = new ConfigurationException();
			ce.initCause(new IllegalArgumentException("Not a ShowRSS URL"));
			throw ce;
		}

		return true;
	}

	/**
	 * @return true if the application has a validated configuration, false
	 *         otherwise
	 */
	public boolean isConfigured() {

		return preferences.getBoolean(PREFERENCE_CONFIGURED, false);
	}

	/**
	 * Mark the application as "configured", so the UI can avoid invoking the
	 * preferences every time you start the application
	 */
	public void setConfigured() throws ConfigurationException {

		boolean validConfiguration = false;

		validConfiguration = checkTorrentFolder(getTorrentFolder())
				&& checkFeedUrl(getFeedUrl())
				&& checkUpdateInterval(getUpdateInterval());

		if (validConfiguration) {
			preferences.putBoolean(PREFERENCE_CONFIGURED, true);
			configurationChanged(PREFERENCE_CONFIGURED);
		}
	}

	/**
	 * @return a string describing when the last feed update took place
	 */
	public String getLastUpdateTime() {

		return preferences.get(PREFERENCE_LAST_UPDATE_TIME,
				DEFAULT_LAST_UPDATE_TIME);
	}

	/**
	 * @param lastUpdateTime
	 *            a time-string with the time of the last feed update
	 */
	public void setLastUpdateTime(String lastUpdateTime) {

		// no validation needed
		preferences.put(PREFERENCE_LAST_UPDATE_TIME, lastUpdateTime);
		configurationChanged(PREFERENCE_LAST_UPDATE_TIME);
	}

	/**
	 * @return a string array containing the URLs of all downloaded torrents,
	 *         sorted by descending download time (newer first)
	 */
	public ArrayList<String> getDownloadedTorrents() {

		String rawDownloaded = preferences.get(PREFERENCE_DOWNLOADED_TORRENTS,
				"");

		if (rawDownloaded.equals(""))
			return DEFAULT_DOWNLOADED_TORRENTS;

		return new ArrayList<String>(Arrays.asList(rawDownloaded.split(",")));
	}

	/**
	 * @param url
	 *            the URL of a downloaded torrent
	 */
	public void addDownloadedTorrent(String url) {

		String rawOldDownloaded = preferences.get(
				PREFERENCE_DOWNLOADED_TORRENTS, "");

		if (rawOldDownloaded.equals("")) {
			// first download
			preferences.put(PREFERENCE_DOWNLOADED_TORRENTS, url);
		} else {
			// organize our data
			ArrayList<String> oldDownloaded = new ArrayList<String>(Arrays
					.asList(rawOldDownloaded.split(",")));

			String newDownloaded = url;

			// check if the download is present, in that case do nothing
			boolean present = false;
			for (String torrent : oldDownloaded) {
				if (torrent.equals(url)) {
					present = true;
				}
			}
			if (present)
				return;

			// avoid overflowing the Preferences API by discarding old downloads
			for (String torrent : oldDownloaded) {
				if (newDownloaded.length() + ",".length() + torrent.length() < Preferences.MAX_VALUE_LENGTH) {
					newDownloaded += "," + torrent;
				}
			}

			// save
			preferences.put(PREFERENCE_DOWNLOADED_TORRENTS, newDownloaded);

		}

		configurationChanged(PREFERENCE_DOWNLOADED_TORRENTS);
	}

	/**
	 * @return true if the last update failed, false otherwise
	 */
	public boolean isLastUpdateFailed() {

		return preferences.getBoolean(PREFERENCE_LAST_UPDATE_FAILED,
				DEFAULT_LAST_UPDATE_FAILED);
	}

	/**
	 * @param failed
	 *            true if the last update failed, false otherwise
	 */
	public void setLastUpdateFailed(boolean failed) {

		// no validation needed
		preferences.putBoolean(PREFERENCE_LAST_UPDATE_FAILED, failed);
		configurationChanged(PREFERENCE_LAST_UPDATE_FAILED);
	}

	/**
	 * @return the current status of the running application
	 */
	public Status getCurrentStatus() {

		return status;
	}

	/**
	 * @param status
	 *            the new status of the running application
	 * @see Status
	 */
	public void setCurrentStatus(Status status) {

		// no validation needed
		this.status = status;
		configurationChanged(PREFERENCE_STATUS);
	}

	/**
	 * @return the current problem list of the running application
	 */
	public ArrayList<Problem> getProblems() {

		return problems;
	}

	/**
	 * @param problem
	 *            the description of a problem that has occurred in the current
	 *            session
	 */
	public void addProblem(Problem problem) {

		// no validation needed
		problems.add(problem);
		configurationChanged(PREFERENCE_PROBLEMS);
	}

	/**
	 * Generic configuration exception, thrown when someone attempts to "set" an
	 * invalid parameter
	 * 
	 * @author Giorgio Calderolla
	 */
	public class ConfigurationException extends IllegalArgumentException {

		private static final long serialVersionUID = 1L;

	}
}
